﻿//+-------------------------------------------------------------------------------------------------------------------------------------------------------
//
//Description:   ParameterizableProperty view model
//Author: Aravind Ramarathinam (aravr@microsoft.com)
//Copyright © Microsoft Corporation
//Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. 
//You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 
//
//THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, 
//INCLUDING WITHOUT LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABLITY OR NON-INFRINGEMENT. 
//See the Apache Version 2.0 License for specific language governing permissions and limitations under the License.
// 
//--------------------------------------------------------------------------------------------------------------------------------------------------------

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using VmroleAuthor.Models;

namespace VmroleAuthor.ViewModels
{
    public abstract class ParameterizableProperty : ViewModelBase, IDataErrorInfo, IDataWarningInfo, ICleanupParamsAndPayload
    {
        private bool isParameterized;
        private string selectedPropertyValue;       
        private readonly ViewDefParamInfo viewDefParamInfo;

        private readonly Func<string> autoGeneratedPropertyParamName;
        private readonly string autoGeneratedPropertyParamDescription;
        private readonly ParameterType autoGeneratedParamType;               
        protected readonly ParametersViewModel ParametersViewModel;
        protected readonly Action<string> ModelValueSetter;
        protected readonly Func<string> ModelValueGetter;
        protected readonly string PropertyName;    

        public static class PropertyNames
        {
            public const string IsParameterized = "IsParameterized";
            public const string PropertyValue = "PropertyValue";
            public const string SelectedPropertyValue = "SelectedPropertyValue";           
        }

        protected ParameterizableProperty(
            string propertyName,          
            Func<string> modelValueGetter,
            Action<string> modelValueSetter,
            Func<string> autoGeneratedPropertyParamName,
            ParameterType autoGeneratedParamType,
            string autoGeneratedPropertyParamDescription,           
            ParametersViewModel parametersViewModel,       
            bool isRequired,
            ViewDefParamInfo viewDefParamInfo          
            )
        {
            this.PropertyName = propertyName;            
            this.ModelValueGetter = modelValueGetter;
            this.ModelValueSetter = modelValueSetter;
            this.autoGeneratedPropertyParamName = autoGeneratedPropertyParamName;
            this.autoGeneratedParamType = autoGeneratedParamType;
            this.autoGeneratedPropertyParamDescription = autoGeneratedPropertyParamDescription;
            this.ParametersViewModel = parametersViewModel;          
            this.IsRequired = isRequired;
            this.viewDefParamInfo = viewDefParamInfo;
       
            List<string> paramNames;
            string newErr = ParameterizationHelper.ValidateAndGetParameterNames(modelValueGetter(), out paramNames);
            this.isParameterized = String.IsNullOrEmpty(newErr) && paramNames.Count > 0;
            if (this.isParameterized)
            {
                // parameterized
                foreach (var paramName in paramNames)
                {
                    this.ParametersViewModel.UpdateViewDefInfoFromParameterDefinitionContext(paramName, new ViewDefParamInfo(this.viewDefParamInfo));
                }             
            }
        }
    
        public bool AcceptsMultilineReturn
        {
            get
            {
                return this.viewDefParamInfo != null && this.viewDefParamInfo.ViewDefParameterType == ViewDefParameterType.MultiLineString;
            }
        }

        public virtual List<string> AvailableParameters
        {
            get 
            { 
                var list =  this.ParametersViewModel.AvailableParameters; 
                if (this.viewDefParamInfo != null && !string.IsNullOrEmpty(this.viewDefParamInfo.DefaultInitialValue))
                {
                    list.Insert(0, this.viewDefParamInfo.DefaultInitialValue);
                }
                return list;
            }
        }

        public bool IsRequired { get; set; }

        public bool IsParameterized
        {
            get { return this.isParameterized; }
            set
            {
                this.isParameterized = value;             
                this.OnPropertyChanged(PropertyNames.PropertyValue);
                this.OnPropertyChanged(PropertyNames.IsParameterized);             
            }
        }

        public string PropertyValue
        {
            get
            {
                return this.ModelValueGetter();
            }
            set
            {                
                this.Set(value);
                this.OnPropertyChanged(PropertyNames.PropertyValue);
                this.OnPropertyChanged(PropertyNames.IsParameterized);                             
            }
        }

        public string SelectedPropertyValue
        {
            get
            {
                return this.selectedPropertyValue;
            }
            set
            {
                if (value != null)
                {
                    this.Set(value);
                }              
                this.selectedPropertyValue = this.PropertyValue;
                this.OnPropertyChanged(PropertyNames.SelectedPropertyValue);               
                //Note: dont do this here -> this.OnPropertyChanged(PropertyName); 
            }
        }

        public void Cleanup()
        {
            this.PropertyValue = null;
        }

        private void Set(string newValue)
        {
            if (newValue != null)
            {
                if (String.Equals(newValue, ParametersViewModel.AutoGenerateMoniker, StringComparison.Ordinal))
                {
                    string autoGenParamHint = this.autoGeneratedPropertyParamName();
                    if (String.IsNullOrEmpty(autoGenParamHint))
                    {
                        Random random = new Random();
                        autoGenParamHint = ParametersViewModel.AutoGeneratedParamPrefix + random.Next();
                    }
                    newValue = ParameterizationHelper.GenerateParameterString(autoGenParamHint);
                }

                if (String.Equals(newValue, this.ModelValueGetter(), StringComparison.Ordinal))
                {
                    // already set, just ignore    
                    return;
                }
            }            

            string oldValue = this.ModelValueGetter();
            if (newValue == string.Empty)
            {              
                newValue = null;
            }
            if (newValue == null)
            {
                if (this.autoGeneratedParamType == ParameterType.Boolean)
                {
                    // Default is False
                    newValue = "false";
                }
            }
            this.ModelValueSetter(newValue);

            List<string> previousParamNamesToRemove;
            ParameterizationHelper.ValidateAndGetParameterNames(oldValue, out previousParamNamesToRemove);
            List<string> newParamNames;
            string newErr = ParameterizationHelper.ValidateAndGetParameterNames(newValue, out newParamNames);
            this.isParameterized = false;
            if (String.IsNullOrEmpty(newErr) && newParamNames.Count > 0)
            {
                // parameterized
                foreach (var paramName in newParamNames)
                {
                    previousParamNamesToRemove.RemoveAll(p => string.Equals(p, paramName, StringComparison.Ordinal));
                    this.ParametersViewModel.AddParameterIfNotExist(paramName, autoGeneratedParamType.ToString(), autoGeneratedPropertyParamDescription, new ViewDefParamInfo(this.viewDefParamInfo));
                }
                this.isParameterized = true;
            }
            //Remove old parameters
            this.ParametersViewModel.RemoveParameters(previousParamNamesToRemove);          
        }      

        public string this[string propName]
        {
            get
            {
                string error = null;
                switch (propName)
                {
                    case PropertyNames.PropertyValue:
                        if (this.IsParameterized)
                        {
                            error = ParameterizationHelper.ValidatePropertyParameterized(this.ModelValueGetter(), this.ParametersViewModel);
                            if (!String.IsNullOrEmpty(error))
                            {
                                error = this.PropertyName + " " + error;                                
                            }
                        }
                        else
                        {
                            error = ParameterizationHelper.ValidatePropertyNotParameterized(this.ModelValueGetter(), this.IsRequired);
                            if (String.IsNullOrEmpty(error))
                            {
                                error = this.DoTypeSpecificValidation();
                            }
                            else
                            {
                                error = this.PropertyName + " " + error;                    
                            }
                        }
                        
                        break;
                }
                return error;
            }
        }

        public string Error { get; set; }


        public abstract string DoTypeSpecificValidation();

        public string GetWarning(string propertyName)
        {
            string warning = null;
            switch (propertyName)
            {
                case PropertyNames.PropertyValue:
                    if (this.viewDefParamInfo.IsRecommendedToBeParameterized &&  this.ModelValueGetter() != null && !ParameterizationHelper.IsFullyParameterized(this.ModelValueGetter()))
                    {
                        warning = "Recommended to parameterize: " + this.PropertyName;
                    }
                    break;
            }
            return warning;           
        }
    }


    public class StringParameterizableProperty : ParameterizableProperty
    {
        public StringParameterizableProperty(string propertyName,                                          
                                             Func<string> modelValueGetter,
                                             Action<string> modelValueSetter,
                                             Func<string> autoGeneratedPropertyParamName,
                                             ParameterType autoGeneratedParamType,
                                             string autoGeneratedPropertyParamDescription,
                                             ParametersViewModel parametersViewModel,                                           
                                             bool isRequired,
                                             ViewDefParamInfo viewDefParamInfo)
            :base (propertyName, modelValueGetter, modelValueSetter, autoGeneratedPropertyParamName, autoGeneratedParamType, autoGeneratedPropertyParamDescription, parametersViewModel,  isRequired, viewDefParamInfo )
        {
            Debug.Assert(autoGeneratedParamType == ParameterType.String || autoGeneratedParamType == ParameterType.SecureString); 
        }


        public override string DoTypeSpecificValidation()
        {
            if (this.IsRequired && this.ModelValueGetter() == null)
            {
                return PropertyName +" Must be specified";
            }
            return null;
        }
    }

    public class DiskParameterizableProperty : ParameterizableProperty
    {
        public DiskParameterizableProperty(string propertyName,
                                             Func<string> modelValueGetter,
                                             Action<string> modelValueSetter,
                                             Func<string> autoGeneratedPropertyParamName,
                                             ParameterType autoGeneratedParamType,
                                             string autoGeneratedPropertyParamDescription,
                                             ParametersViewModel parametersViewModel,
                                             bool isRequired,
                                             ViewDefParamInfo viewDefParamInfo)
            : base(propertyName, modelValueGetter, modelValueSetter, autoGeneratedPropertyParamName, autoGeneratedParamType, autoGeneratedPropertyParamDescription, parametersViewModel, isRequired, viewDefParamInfo)
        {
            Debug.Assert(autoGeneratedParamType == ParameterType.String);
        }


        public override string DoTypeSpecificValidation()
        {
            if (this.IsRequired && this.ModelValueGetter() == null)
            {
                return PropertyName + " Must be specified";
            }
            string input = this.ModelValueGetter();
            if (input != null)
            {
                string[] words = this.ModelValueGetter().Split(new[] { ':' }, 2);
                if (words.Length != 2)
                {
                    return PropertyName + " Incorrect disk format. Specify disk in the form of Name:Version";
                }
                Version ver;
                if (!Version.TryParse(words[1], out ver))
                {
                    return PropertyName + " Incorrect disk version format. While specifying disk version in Name:Version, version is required to be of Major.MajorReversion.Minor.MinorRevision";
                }
                string[] versionFields = words[1].Split(new[] { '.' });
                if (versionFields.Length != 4)
                {
                    return PropertyName + " While specifying disk version in Name:Version, version is required to be of Major.MajorReversion.Minor.MinorRevision";
                }
            }
            return null;
        }
    }
   
    public class BoolParameterizableProperty : ParameterizableProperty
    {
        public BoolParameterizableProperty(string propertyName,
                                             Func<string> modelValueGetter,
                                             Action<string> modelValueSetter,
                                             Func<string> autoGeneratedPropertyParamName,
                                             ParameterType autoGeneratedParamType,
                                             string autoGeneratedPropertyParamDescription,
                                             ParametersViewModel parametersViewModel,                                            
                                             bool isRequired,
                                             ViewDefParamInfo viewDefParamInfo)
            : base(propertyName, modelValueGetter, modelValueSetter, autoGeneratedPropertyParamName, autoGeneratedParamType, autoGeneratedPropertyParamDescription, parametersViewModel, isRequired, viewDefParamInfo /* defaultInitialValue ? "true" : "false" */)
        {
            Debug.Assert(autoGeneratedParamType == ParameterType.Boolean);
            Debug.Assert(viewDefParamInfo.ViewDefParameterType == ViewDefParameterType.Boolean);
            bool boolResult;
            if (bool.TryParse(this.ModelValueGetter(), out boolResult))
            {
                // False will not deserialize, but false would   
                this.ModelValueSetter(boolResult ? "true" : "false");
                this.OnPropertyChanged(PropertyNames.PropertyValue);
            }     
        }

        public override List<string> AvailableParameters
        {
            get
            {
                var list = this.ParametersViewModel.AvailableParameters;
                list.Insert(0, "false");
                list.Insert(1, "true");              
                return list;
            }
        }

        public override string DoTypeSpecificValidation()
        {
            string input = this.ModelValueGetter();           
            if (input != null)
            {
                // False will not deserialize, but false would                         
                if (string.CompareOrdinal(input, "false") != 0 && string.CompareOrdinal(input, "true") != 0)
                {
                    return PropertyName + " Not a valid boolean. Specify either false or true";
                }
                return null;
            }
            if (this.IsRequired)
            {
                return PropertyName + " Must be specified";
            }
            return null;
        }
    }


    public class IntParameterizableProperty : ParameterizableProperty
    {
        public IntParameterizableProperty(string propertyName,
                                             Func<string> modelValueGetter,
                                             Action<string> modelValueSetter,
                                             Func<string> autoGeneratedPropertyParamName,
                                             ParameterType autoGeneratedParamType,
                                             string autoGeneratedPropertyParamDescription,
                                             ParametersViewModel parametersViewModel,                                       
                                             bool isRequired,
                                             ViewDefParamInfo viewDefParamInfo)
            : base(propertyName, modelValueGetter, modelValueSetter, autoGeneratedPropertyParamName, autoGeneratedParamType, autoGeneratedPropertyParamDescription, parametersViewModel, isRequired, viewDefParamInfo /*defaultInitialValue.ToString() */)
        {
            Debug.Assert(autoGeneratedParamType == ParameterType.Int);
            Debug.Assert(viewDefParamInfo.ViewDefParameterType == ViewDefParameterType.Number);
        }

        public override string DoTypeSpecificValidation()
        {
            string input = this.ModelValueGetter();
            if (this.IsRequired && string.IsNullOrEmpty(input))
            {
                return PropertyName + " Must be specified";
            }
            if (input != null)
            {
                int r;
                if (!Int32.TryParse(input, out r))
                {
                    return PropertyName + " Not a valid integer";
                }
                return null;
            }            
            return null;
        }
    }  

    public class CredentialParameterizableProperty : ParameterizableProperty
    {
        public CredentialParameterizableProperty(string propertyName,
                                             Func<string> modelValueGetter,
                                             Action<string> modelValueSetter,
                                             Func<string> autoGeneratedPropertyParamName,
                                             ParameterType autoGeneratedParamType,
                                             string autoGeneratedPropertyParamDescription,
                                             ParametersViewModel parametersViewModel,                                         
                                             bool isRequired,
                                             ViewDefParamInfo viewDefParamInfo)
            : base(propertyName, modelValueGetter, modelValueSetter, autoGeneratedPropertyParamName, autoGeneratedParamType, autoGeneratedPropertyParamDescription, parametersViewModel, isRequired, viewDefParamInfo)
        {
            Debug.Assert(autoGeneratedParamType == ParameterType.Credential);
            Debug.Assert(viewDefParamInfo.ViewDefParameterType == ViewDefParameterType.Credential);
        }

        public override string DoTypeSpecificValidation()
        {
            string input = this.ModelValueGetter();
            if (this.IsRequired && string.IsNullOrEmpty(input))
            {
                return PropertyName + " Must be specified";
            }
            if (input != null)
            {
                string[] words = this.ModelValueGetter().Split(new[] { ':' }, 2);
                if (words.Length != 2)
                {
                    return PropertyName + " Incorrect credential format";
                }
            }          
            return null;           
        }
    }

    public class EnumParameterizableProperty: ParameterizableProperty
    {
        private readonly Type enumType;

        public EnumParameterizableProperty(string propertyName,
                                             Func<string> modelValueGetter,
                                             Action<string> modelValueSetter,
                                             Func<string> autoGeneratedPropertyParamName,
                                             ParameterType autoGeneratedParamType,
                                             string autoGeneratedPropertyParamDescription,
                                             ParametersViewModel parametersViewModel,  
                                             Type enumType,                              
                                             bool isRequired,
                                             ViewDefParamInfo viewDefParamInfo
                                             )
            : base(propertyName, modelValueGetter, modelValueSetter, autoGeneratedPropertyParamName, autoGeneratedParamType, autoGeneratedPropertyParamDescription, parametersViewModel, isRequired, viewDefParamInfo)
        {
            this.enumType = enumType;
            Debug.Assert(viewDefParamInfo.ViewDefParameterType == ViewDefParameterType.Option || viewDefParamInfo.ViewDefParameterType == ViewDefParameterType.VMSize);
        }

        public new List<string> AvailableParameters
        {
            get
            {
                List<string> avl = new List<string>(Enum.GetNames(this.enumType));
                avl.AddRange(this.ParametersViewModel.AvailableParameters);
                return avl;
            }
        }

        public override string DoTypeSpecificValidation()
        {
            string input = this.ModelValueGetter();
            if (this.IsRequired && string.IsNullOrEmpty(input))
            {
                return PropertyName + " Must be specified";
            }
            if (input != null)
            {
                return ValidationHelper.ValidateEnum(this.ModelValueGetter(), this.enumType);
            }           
            return null; 
        }
    }

}
